#include "clocolips.h"
#include "ttg_ips/define_cmd.h"
#include "ttg_ips/cpackagecrc.h"
#include "serversetting.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define KNX_RECV "/tmp/recv_fifo"
#define KNX_SEND "/tmp/send_fifo"

bool CLocolIPS::gSendRecord = false;
static unsigned int g_knx_src_addr = 0xfffc;

CLocolIPS::CLocolIPS(QObject *parent) :
    QThread(parent)
{
    _readFd = -1;
    _writeFd = -1;

    resetChannel();

    peerPort = 11122;

    localControl = NULL;

    g_knx_src_addr = ServerSetting::defaultSetting()->_src_addr;
    localControl = new QUdpSocket(this);
    if(localControl->bind(peerPort))
    {
        connect(localControl,SIGNAL(readyRead()),this,SLOT(doReadLocalUdpData()));
    }
    else
        qDebug() << "CLocolIPS:localControl bind fail!";
}

void CLocolIPS::resetChannel()
{
    if(_writeFd<0)
    {
        _writeFd = open(KNX_SEND,O_RDWR);
        if(_writeFd<0)
            qDebug() << "open channel fail:" << KNX_SEND;
    }

    if(_readFd < 0)
    {
        _readFd = open(KNX_RECV,O_RDWR);
        if(_readFd < 0)
            qDebug() << "open channel fail:" << KNX_RECV;
        else
        {
            _knxRcvnotifier = new QSocketNotifier(_readFd, QSocketNotifier::Read, this);
            connect(_knxRcvnotifier,SIGNAL(activated(int)),this,SLOT(readDataChannel(int)));
        }
    }

    if(_readFd > 0 && _writeFd > 0)
    {
        init = true;
    }
    else
    {
        init = false;
    }
}

void CLocolIPS::updateIpsState(int state)
{
    static int lastState = 5;
    if(lastState!=state)
    {
        lastState = state;
        emit s_gateWayConnectState(lastState);
    }
}

QList<QByteArray> CLocolIPS::splitDatagram(unsigned char *data,int size)
{
    QList<QByteArray> list;

    unsigned char *start = data;

    while(size>0)
    {
        if(start[0] == 0xAA && start[1] == 0x55)
        {
            unsigned int total_size = start[2];
            QByteArray cmei_data = QByteArray((char *)start,total_size);
            list.append(cmei_data);

            start += total_size;
            size -= total_size;
        }
        else
            break;
    }

    //qDebug() << "splitDatagram data:" << list.size();

    return list;
}

void CLocolIPS::doReadLocalUdpData()
{
    QUdpSocket *s = (QUdpSocket *)QObject::sender();

    if(s->pendingDatagramSize()>4)
    {
        QByteArray cmdBytes;
        cmdBytes.resize(s->pendingDatagramSize());

        QHostAddress host;
        quint16 port;

        s->readDatagram(cmdBytes.data(),cmdBytes.size(),&host,&port);

        static QString replay = QString("TTG-RemoteGateway ver.%1 \n eth0:%2")
                .arg(g_version).arg(ServerSetting::defaultSetting()->getHostIpString("eth"));

        s->writeDatagram(replay.toLocal8Bit(),host,port);

        QString upMsg = QString(cmdBytes);
        QStringList uplist = upMsg.split("#");
        if(uplist.size() != 2)
        {
            return ;
        }

        QString address = uplist.at(0);
        QString value = uplist.at(1);

        char strAddr[2] = {0};

        cmei_set_group_address((_u8 *)strAddr,address.toUtf8());
        QByteArray addressByte((char *)strAddr,2);

        QList<DEVICE_OBJECT *> regist_list = _registersMap.values(addressByte);
        //print_data(str_group_addr,device_data,16);
        if(regist_list.size()>0)
        {
            for (int i = 0; i < regist_list.size(); ++i) {
                DEVICE_OBJECT *obj = (DEVICE_OBJECT *)regist_list.at(i);

                emit s_valueNotify_debug((void *)obj,value,obj->_user_data);
            }
        }
    }
}

void CLocolIPS::run()
{

}

void CLocolIPS::readDataChannel(int fd)
{
    if (fd>0)
    {
        _u8 buffer[256] = {0};
        int len = read(fd,buffer,256);

        //qDebug() << "CNetwork2Locol: readDataChannel length " <<len;

        QList<QByteArray> cmdList = splitDatagram((unsigned char *)buffer,len);

        for(int i=0;i<cmdList.size();i++)
        {
            QByteArray d = cmdList.at(i);
            ttg_data_analyze((_u8 *)d.data(),d.size());
        }

        cmdList.clear();
        //udp->writeDatagram(buffer,len,peerAddr,port);
    }
}

void CLocolIPS::doReadDevice(QByteArray addr2bytes)
{
    ttg_read_status((_u8 *)addr2bytes.data());
}

void CLocolIPS::doWriteDevice(QByteArray addr2bytes, int data_type, QByteArray data)
{
    ttg_write_device((_u8 *)addr2bytes.data(),data_type,(_u8 *)data.data(),data.size());
}

void CLocolIPS::doRegistObject(void *dev, void *userdata)
{
    if(!registeObject(dev,userdata))
        qDebug() << "CLocolIPS:doRegistObject fail!";
}

_int32 CLocolIPS::ttg_write_device(_u8 addr[2],_u8 value_type,_u8 *data,_u32 len)
{
    _int32 msg_len = 0;

    _u8 common_buffer_ttg[32] = {0};
    _u8 common_data_buffer[32] = {0};

    common_data_buffer[0] = APCI_VALUE_WRITE;

    if (value_type == ONE_BIT_VALUE) {
        common_data_buffer[0] |= (*data & 0x1);
        msg_len = ttg_data_package(addr,common_data_buffer,1,common_buffer_ttg);
    }
    else
    {
        memcpy(&common_data_buffer[1], data, len);
        msg_len = ttg_data_package(addr,common_data_buffer,len+1,common_buffer_ttg);
    }

    return ttg_send_msg((char *)common_buffer_ttg,msg_len);

}

_int32 CLocolIPS::ttg_send_msg(char *buff,int size)
{
    QWriteLocker locker(&lock);

    QThread::msleep(100);

    if(_writeFd > 0)
        return write(_writeFd,buff,size);
    else
    {
        qDebug() << "CLocolIPS:write channel not open!";

        if(localControl!=NULL)
            localControl->writeDatagram(buff,size,peerHost,peerPort);

        return 0;
    }
}

_int32 CLocolIPS::ttg_read_status(_u8 addr[])
{
    char readAddr[16] = {0};
    cmei_get_string_group_address(readAddr,addr);

    //qDebug() << "read address " << readAddr;

    _u8 g_common_read_buff[32] = {0};
    _u8 g_common_read_buff_ttg[32] = {0};

    g_common_read_buff[0] = APCI_VALUE_READ;
    _u8 msg_len = ttg_data_package(addr,g_common_read_buff,1,g_common_read_buff_ttg);

    return ttg_send_msg((char *)g_common_read_buff_ttg,msg_len);
}

unsigned short CLocolIPS::ttg_data_package(_u8 addr[2], _u8 *value, char value_len, _u8 *buff)
{
    int size = set_msg_header((char *)buff);

    _u8 *cemi_data = (_u8 *)&buff[size];

    // stander cemi data
    int cmei_size = package_cmei_msg_ttg(cemi_data,0xBC,addr,value,value_len);
    cemi_data[cmei_size] = calc_onebyte_crc(cemi_data,cmei_size);
    cmei_size += 1;

    cemi_data[cmei_size] = calc_onebyte_crc(cemi_data,cmei_size);
    cmei_size += 1;

    size += cmei_size;  // last one is rand number

    set_package_length((char *)buff,size);

    return size;
}

_u8 CLocolIPS::package_cmei_msg_ttg(_u8 msg[],_u8 controlField1,_u8 addr[2],_u8 *value,_u8 size)
{
    // cEMI frame ----------------------------------------------

    //    int controlField1 = 0xba;      //not repeat, low
    //    int controlField2 = 0xd0;      //group address
    msg[0] = controlField1;

    //0xff 0xfe ipad default , all 0 is boardcast address
    msg[1] = (g_knx_src_addr>>8)&0xff;     // source address hi byte  (to be filled by gateway)
    msg[2] = g_knx_src_addr & 0xff;     // source address lo byte  (to be filled by gateway)

    msg[3] = addr[0];     // dest address hi byte
    msg[4] = addr[1];     // dest address lo byte

    msg[5] = 0xE0|(size&0xf);
    msg[6] = 0x00;

    memcpy(&msg[7], value, size);

    return 7+size;
}

bool CLocolIPS::ttg_data_crc_verify(_u8 *data, _u8 size)
{
    // AA 55 , length
    _u8 *cstart = data + 3;
    size -= 3;

    _u8 crc = calc_onebyte_crc(cstart,size-1);

    if(cstart[size-1] == crc)
    {
        return true;
    }

    return false;
}

_int32 CLocolIPS::ttg_cmei_data_analyze(_u8 msg[],_u8 msg_len)
{
    //AA 55 00 15 31 31 31 31 31 31 D4 CC 99 20 80 01 E0 08 0A 01 01 BD BD BB ED

    if(msg_len == 7 && msg[3] == 0xff && msg[4] == 0xff)
    {
        //qDebug() << "ttg_cmei_data_analyze: knx INDICATION " <<  QByteArray((const char *)&msg[5],1).toHex();
        return 0;
    }

    if(msg_len < 12)
    {
        QByteArray d = QByteArray((const char*)msg,msg_len);
        qDebug() << "ttg_cmei_data_analyze: size too short:" << d.toHex();

        return -1;
    }

    QByteArray strAddr;
    int cemi_index = 3 ;
    _u8 cemi_size = msg[2];
    // 2 head, 1 crc
    cemi_size -= 3;

    _u8 *device_data = &msg[cemi_index];
    _u8 *dest_addr = &device_data[3];

    char str_group_addr[24] = {0};
    cmei_get_string_group_address(str_group_addr,dest_addr);
    strAddr = QByteArray((char *)dest_addr,2);

    if(gSendRecord)
    {
        char source_group_addr[24] = {0};

        _u8 *source_addr = &device_data[1];
        cmei_get_string_phy_address(source_group_addr,source_addr);

        QString record = QString(source_group_addr) + " -> " + QString(str_group_addr)
                + " = " +QByteArray((const char *)&device_data[5],cemi_size-5).toHex();

        emit s_recordMsg(record);
    }

    qDebug() << "TTG_routing_INDICATION: dest addrss " << QString(str_group_addr);

    QList<DEVICE_OBJECT *> regist_list = _registersMap.values(strAddr);
    //print_data(str_group_addr,device_data,16);
    if(regist_list.size()>0)
    {
        _u8 *device_value = NULL,value_len = 0;
        DEVICE_OBJECT *obj = NULL;
        _u8 operation_type = 0;

        //bc 2 srouce , 2 dest,
        device_value = &device_data[5];

        for (int i = 0; i < regist_list.size(); ++i) {
            obj = (DEVICE_OBJECT *)regist_list.at(i);

            _u8 *ret_value = NULL;

            routing_indication_recv_analyze(device_value,obj->_data_type,&operation_type,
                                            &ret_value,&value_len);

            _int32 source_addr_empty = FALSE;
            if (device_data[1] == 0 && device_data[2] == 0) {
                source_addr_empty = TRUE;
            }

            //aa 55 0d bc 11 03 0d 38 e1 00 81 04 00
            //aa 55 0d bc s1 s2 d1 d2 valuesize tpci/apci r/w value crc
            //0x0d=1 0x0e=2, 12 is the size without value
            if(msg_len - value_len < 12)
            {
                QByteArray d = QByteArray((const char*)msg,msg_len);
                qDebug() << "ttg_cmei_data_analyze: data not correct:" << msg_len - value_len << " - " << d.toHex();

                return FAIL ;
            }

            if(operation_type == APCI_VALUE_RESP ||
               (operation_type == APCI_VALUE_WRITE && source_addr_empty == FALSE))
            {
                message_device_data_handle(obj,ret_value,value_len);
            }
        }

    }

    return SUCCESS;
}

_int32 CLocolIPS::message_device_data_handle(DEVICE_OBJECT *data,_u8 *value,_u8 value_len)
{
    if (data!=NULL && value!=NULL) {
        QByteArray valueByte;

        switch (data->_data_type) {
            case ONE_BIT_VALUE:
            {
                valueByte = QByteArray((char *)value,1);
                //KDEBUG << "onebit_callback:" << data->_address << " = " << valueByte.toUInt();
                //emit engineValueNotify(data->_address,ONE_BIT_VALUE,valueByte,data->_user_data);

                break;
            }

            case ONE_BYTE_VALUE:
            case TWO_BYTES_VALUE:
            case THREE_BYTES_VALUE:
            case FOUR_BYTES_VALUE:
            {
                //skip APCI/APEI
                value ++;
                value_len --;

                valueByte = QByteArray((char *)value,value_len);
                KDEBUG << "bytes_callback:" <<  data->_address << " hex = " << valueByte.toHex();
                //KDEBUG << "bytes_callback:" <<  data->_address << "len = " << value_len;
                //emit engineValueNotify(data->_address,data->_data_type,QByteArray((char *)value,value_len),data->_user_data);

                break;

            }

            default:
            {
                KDEBUG << "recv_msg_handle: error data type " << data->_data_type;
                return -1;

            }

        }

        qDebug() << "value call back: " << data->_address << "size :" << valueByte.size() ;

        emit s_valueNotify((void *)data,valueByte,data->_user_data);
    }

    return 0;
}

_int32 CLocolIPS::routing_indication_recv_analyze(_u8 data[],_u32 data_type,_u8 *operation_type,
                                       _u8 **ret_value,_u8 *ret_len)
{
    _u8 len = data[0]&0xf;     // data len
    //_u8 tpci = data[1];      // TPCI/APCI
    _u8 *value = &data[2];
    *ret_value = value;
    *ret_len = len;

    if ( (*value&APCI_VALUE_RESP) == APCI_VALUE_RESP) {
        *operation_type = APCI_VALUE_RESP;
    }
    else if ((*value&APCI_VALUE_WRITE) == APCI_VALUE_WRITE)
    {
        *operation_type = APCI_VALUE_WRITE;
    }
    else
    {
        *operation_type = APCI_VALUE_READ;
    }

    return SUCCESS;
}

void CLocolIPS::ttg_data_analyze(_u8 msg[],_u8 msg_len)
{
    if(msg_len<7)
    {
        qDebug() << "ttg_data_analyze: data size too short " << msg_len;
        return;
    }

    unsigned int total_size = msg[2];
    if(msg_len!= total_size)
    {
        qDebug() << "ttg_data_analyze: data format of status is not correct "
                 << msg_len << "!=" << total_size;
        return;
    }

    if(msg[0] == 0xAA && msg[1] == 0x55)
    {
        bool vr = ttg_data_crc_verify(msg,msg[2]);

        if(!vr)
        {
            QByteArray d = QByteArray((const char*)msg,msg_len);
            qDebug() << "ttg_data_crc_verify: CRC fail - " << d.toHex();
        }
        else
        {
            ttg_cmei_data_analyze(msg,msg_len);
        }
    }
}

void CLocolIPS::set_package_length(char *buff_head, _u8 length)
{
    buff_head[2] = length;
}

unsigned int CLocolIPS::set_msg_header(char *buff)
{
    //2 bytes
    buff[0] = 0xAA;
    buff[1] = 0x55;
    buff[2] = 0xff;  //default padding length

    return 3;
}

_u8 CLocolIPS::calc_onebyte_crc(_u8 *buff,_u8 size)
{
    int i =0;
    char crc = 0;
    for (i=0; i<size; i++) {
        crc^= buff[i];
    }

    crc = ~crc;

    return crc;
}

bool CLocolIPS::registeObject(void *devObj, void *userdata)
{
    DEVICE_OBJECT *obj = (DEVICE_OBJECT *)devObj;

    KDEBUG << QString("registeObject: dest addrss") << obj->_address.data() << obj->_data_type;

    obj->_user_data = userdata;

    _registersMap.insertMulti(QByteArray((char *)obj->_ga_ia,2) ,obj);

    return true;
}

//根据地址和控件来取消
bool CLocolIPS::unRegisterObject(void *devObj,void *userdata)
{
    DEVICE_OBJECT * objs = (DEVICE_OBJECT *)devObj;

    QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator i = _registersMap.begin();
    while (i != _registersMap.end()) {
        QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator prev = i;
        ++i;
        if (prev.key() == QByteArray((char *)objs->_ga_ia,2))
        {
            DEVICE_OBJECT *o = prev.value();
            //服务器端,控件是不存在的,可能为 NULL
            if(o && o == objs)
            {
                _registersMap.erase(prev);
                return true;
            }
            else
                continue;
        }
    }

    return false;
}

void CLocolIPS::unRegisterAll()
{
    _registersMap.clear();
}
